﻿using System;
using System.Drawing;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;
using Glider.Common.Objects;

namespace AdvancedMap
{
    public partial class Map : Form
    {
        #region vars

        //Global vars
        private static bool time = true;
        private static bool debug = true;
        private static bool UpdateFormSize = true;


        private string mapZone = GContext.Main.WorldMap;

        public static AMSettings MyAM = new AMSettings();

        private static Int32 iMonsterMinLvl;
        private static Regex rFilterPlayers;
        private static Regex rFilterNPCs;
        private static Regex rFilterMonsters;
        private static Regex rFilterHerbs;
        private static Regex rFilterMinerals;
        private static Regex rFilterTreasure;
        private static Regex rFilterMisc;
        private static bool showPlayers;
        private static bool showMonsters;
        private static bool showNodes;
        private static int corpseCount = 0;

//        public static Predicate<GPlayer> predPlayer;
//        public static Predicate<GMonster> predMonster;
//        public static Predicate<GNode> predNode;

        private static Random rnd = new Random();

        const float TILESIZE = (float)(1600/3); // 533.33333;
        const float CHUNKSIZE = (float)TILESIZE / (float)16;
        const float UNITSIZE = (float)CHUNKSIZE / (float)8;
        const float ZEROPOINT = (float)32 * TILESIZE;
        const float IMAGESIZE = 256;

        //Threading vars
        private static Thread mapThread;
        private static Thread updateThread;
        private static AdvancedMap.Map advMap;
        private TileBlock.TileBlock tiles;
        private QueuedSounds soundQueue = new QueuedSounds();
        private static string targetFile = (AppDomain.CurrentDomain.BaseDirectory + @"\Classes\Maps\AdMap.xml");

        //Write to invisible bitmap
        Bitmap offScreenBmp = null;
        Graphics offScreenDC;

        //Icon Bitmap
        Bitmap img;
        
        #endregion

        #region methods

        public Map()
        {
            try
            {
                InitializeComponent();
                this.menuStrip1. ..DropShadowEnabled = false;
                this.ShowInTaskbar = false;
                //this.mapSelect.Text = "None";
                //GLog("Update Config Values");

                MyAM = new AMSettings();
                AMSettings SavedSettings = MyAM.Load<AMSettings>(targetFile);
                if (SavedSettings != null)
                {
                    MyAM = SavedSettings;
                    GLog("AdvMap: Loaded settings from " + targetFile);
                }
                //MyAM.Dump();
                //GLog("Setting Button Colors");
                UpdateUIVars();
                SetButtonColor(this.btnUseFilters, MyAM.useFilters);
                SetButtonColor(this.btnSound, MyAM.playSound);

                tiles = new TileBlock.TileBlock();
                
            }
            catch (Exception e)
            {
                GLog("Error in Map()");
                GLog(e.ToString());
                GLog(e.Message.ToString());
                GLog(e.StackTrace.ToString());
            }
        }

        // Glider X to Map X
        private int TileX()
        {
            return (int)((32 * TILESIZE - GPlayerSelf.Me.Location.Y) / TILESIZE);
        }

        // Glider Y to Map Y
        private int TileY()
        {
            return (int)((32 * TILESIZE - GPlayerSelf.Me.Location.X) / TILESIZE);
        }

        // Switch Y to X (center)
        private float PlayerX()
        {
            return GPlayerSelf.Me.Location.Y;
        }

        // Switch Y to X (zero point)
        private float PlayerPosX()
        {
            return (float)32 * (float)TILESIZE - GPlayerSelf.Me.Location.Y;
        }

        // Switch X to Y (center)
        private float PlayerY()
        {
            return GPlayerSelf.Me.Location.X;
        }

        // Switch X to Y (zero point)
        private float PlayerPosY()
        {
            return (float)32 * (float)TILESIZE - GPlayerSelf.Me.Location.X;
        }

        // Public, initialize threads
        public void RunMap()
        {
            try
            {
                GLog("Starting AdvMap threads...");
                advMap = this;
                mapThread = new Thread(advMap.MapThread);
                updateThread = new Thread(advMap.UpdateThread);

                if (MyAM.debug)
                {
                    Log("mapThread ID: " + Convert.ToString(mapThread.GetHashCode()));
                    Log("updateThread ID: " + Convert.ToString(updateThread.GetHashCode()));
                }

                mapThread.Start();
                updateThread.Start();
            }
            catch (Exception e)
            {
                GLog("Error in RunMap()");
                GLog(e.ToString());
                GLog(e.Message.ToString());
                GLog(e.StackTrace.ToString());
            }
        }


        // Public, kill map
        public void StopMap()
        {
            try
            {
//                SaveToFile(targetFile);
                MyAM.Save(targetFile);
                Log("Stopping threads...");

                soundQueue.Dispose();

                //updateThread.
                updateThread.Abort();
                updateThread.Join(); //.Abort(300);

                //mapThread.
                mapThread.Abort();
                mapThread.Join(); //.Abort(300);

                advMap.Close();
            }
            catch (Exception e)
            {
                Log("Error in StopMap()");
                Log(e.ToString());
                Log(e.Message.ToString());
                Log(e.StackTrace.ToString());
            }
        }


        // Runs the map GUI
        private void MapThread()
        {
            try
            {
                Application.EnableVisualStyles();
                Application.DoEvents();
                Application.Run(this);
            }
            catch
            {
                Log("Error in MapThread()");
            }
        }

        // ?
        private int OffsetY(float obj, float me)
        {
            return this.Width / 2 - Convert.ToInt32((obj - me) * (IMAGESIZE / TILESIZE) * (float)MyAM.scale);
        }

        // ?
        private int OffsetX(float obj, float me)
        {
            return this.Height / 2 - Convert.ToInt32((obj - me) * (IMAGESIZE / TILESIZE) * (float)MyAM.scale);
        }

        // Glider heading to C# heading
        private double ConvertHeading(double heading)
        {
            return heading * -1 - Math.PI / 2;
        }

        // Glider heading to C# heading
        private float ConvertHeadingDeg(double heading)
        {
            return (float)((heading * -1 /*- Math.PI / 2*/) * (180/Math.PI));
        }

        private string PlayerClass (GPlayerClass PClassType)
        {
            string retClass = "";

            switch (PClassType) {
                case GPlayerClass.Deathknight:
                    retClass = "DKN";
                    break;
                case GPlayerClass.Druid:
                    retClass = "DRU";
                    break;
                case GPlayerClass.Hunter:
                    retClass = "HUN";
                    break;
                case GPlayerClass.Mage:
                    retClass = "MAG";
                    break;
                case GPlayerClass.Paladin:
                    retClass = "PAL";
                    break;
                case GPlayerClass.Priest:
                    retClass = "PRI";
                    break;
                case GPlayerClass.Rogue:
                    retClass = "ROG";
                    break;
                case GPlayerClass.Shaman:
                    retClass = "SHA";
                    break;
                case GPlayerClass.Warlock:
                    retClass = "WLK";
                    break;
                case GPlayerClass.Warrior:
                    retClass = "WAR";
                    break;
                default:
                    retClass = "UNK";
                    break;
            }
            return retClass;
        }

        private string CreatureType (GCreatureType UCreatureType)
        {
            string retType = "";

            switch (UCreatureType) {
                case GCreatureType.Beast:
                    retType = "BEA";
                    break;
                case GCreatureType.Critter:
                    retType = "CRIT";
                    break;
                case GCreatureType.Demon:
                    retType = "DEM";
                    break;
                case GCreatureType.Dragonkin:
                    retType = "DRAG";
                    break;
                case GCreatureType.Elemental:
                    retType = "ELEM";
                    break;
                case GCreatureType.Giant:
                    retType = "GNT";
                    break;
                case GCreatureType.Humanoid:
                    retType = "HUM";
                    break;
                case GCreatureType.Mechanical:
                    retType = "MEC";
                    break;
                case GCreatureType.NoDefinition:
                    retType = "UNK";
                    break;
                case GCreatureType.NonCombatPet:
                    retType = "NCP";
                    break;
                case GCreatureType.NotSpecified:
                    retType = "UNK";
                    break;
                case GCreatureType.Totem:
                    retType = "TOT";
                    break;
                case GCreatureType.Undead:
                    retType = "UND";
                    break;
                case GCreatureType.Unknown:
                    retType = "UNK";
                    break;
                default:
                    break;
            }
            return retType;
        }

        private void PrintViewCircle()
        {
            int radius = (int)(MyAM.circleViewSize * MyAM.scale);
            Pen myPen = new Pen(MyAM.colorViewCircle);
            offScreenDC.DrawEllipse(myPen, this.Width / 2 - radius, this.Height / 2 - radius, 2 * radius, 2 * radius);
        }


        // Print self
        private void PrintMe()
        {
            PrintViewCircle();
            Color corpseColor;

            try
            {
                //Print Me
                if (MyAM.showMe)
                {
                    string myTop = "";
                    string myBot = "";
                    if (MyAM.showNames)
                    {
                        if (MyAM.labelTop.Length == 0)
                            myTop = "Me"; //GPlayerSelf.Me.Name;
                        else
                            myTop = MyAM.labelTop;
                    }
                    myBot = GPlayerSelf.Me.Level.ToString() + " ";
                    if (MyAM.showClass)
                    {
                        if (MyAM.labelBot.Length == 0)
                            myBot = myBot + PlayerClass(GPlayerSelf.Me.PlayerClass) + " ";
                        else
                            myBot = MyAM.labelBot;
                    }
                    //           if (MyAM.showHealth) myBot = myBot + Convert.ToInt16(GPlayerSelf.Me.Health * 100).ToString();
                    //           myBot = myBot.TrimEnd();

                    if (MyAM.sourceIcons == IconSource.UseIcons)
                    {
                        Bitmap bmpImg = RotateImage(img, ConvertHeadingDeg(GPlayerSelf.Me.Heading));
                        offScreenDC.DrawImage(bmpImg, this.Width / 2 - bmpImg.Width / 2, this.Height / 2 - bmpImg.Height / 2);
                        PrintUnitInfo(MyAM.colorMe, this.Width / 2, this.Height / 2, myTop, myBot);
                    }
                    else
                    {
                        PrintArrow(MyAM.colorMe, this.Width / 2, this.Height / 2, GPlayerSelf.Me.Heading,
                            myTop, myBot);
                    }
                    if (GPlayerSelf.Me.IsDead)
                    {
                        switch (corpseCount)
                        {
                            case 0:
                                corpseColor = Color.Silver;
                                corpseCount = 1;
                                break;
                            case 1:
                                corpseColor = Color.Snow;
                                corpseCount = 2;
                                break;
                            case 2:
                                corpseColor = Color.Black;
                                corpseCount = 0;
                                break;
                            default:
                                corpseColor = Color.Black;
                                corpseCount = 0;
                                break;
                        }
                        offScreenDC.DrawString("*X*", MyAM.FontText, new SolidBrush(corpseColor),
                        new PointF(OffsetY(GPlayerSelf.Me.CorpseLocation.Y, GPlayerSelf.Me.Location.Y),
                            OffsetX(GPlayerSelf.Me.CorpseLocation.X, GPlayerSelf.Me.Location.X)));
                    }
                }
            }
            catch (Exception e)
            {
                Log("Error in PrintMe()");
                Log(e.ToString());
                Log(e.Message.ToString());
                Log(e.StackTrace.ToString());
            }
        }

        // Print other players
        private void PrintPlayers()
        {
            Color tempColor;
            string othTop = "";
            string othBot = "";
            bool useFilters = false;
            bool playFriends;
            bool playEnemies;

            useFilters = MyAM.useFilters && (MyAM.FilterPlayers.Length > 0);
            playFriends = false;
            playEnemies = false;
            //Print players

            foreach (GPlayer selectPlayer in GObjectList.GetPlayers())
            {
                if (selectPlayer == GPlayerSelf.Me)
                    continue;

                if (useFilters)
                {
                    if (!(rFilterPlayers.Match(selectPlayer.Name).Success))
                        continue;
                } 
                
                if (selectPlayer.IsSameFaction && MyAM.showFriends)
                {
                    tempColor = MyAM.colorFriends; // DefColors.FriendPlayer;
                    if (MyAM.playSound && (MyAM.soundfileFriends.Length > 0)) playFriends = true;
                }
                else if (!selectPlayer.IsSameFaction && MyAM.showEnemies)
                {
                    tempColor = MyAM.colorEnemies; // DefColors.EnemyPlayer;
                    if (MyAM.playSound && (MyAM.soundfileEnemies.Length > 0)) playEnemies = true;
                }
                else continue;

                if (MyAM.showNames) othTop = selectPlayer.Name;
                othBot = selectPlayer.Level.ToString() + " ";
                if (MyAM.showClass) othBot = othBot + PlayerClass(selectPlayer.PlayerClass) + " ";
                if (MyAM.showHealth) othBot = othBot + Convert.ToInt16(selectPlayer.Health * 100).ToString();
                othBot = othBot.TrimEnd();

                if (MyAM.sourceIcons == IconSource.UseIcons)
                {
                    Bitmap bmpImg = RotateImage(img, ConvertHeadingDeg(selectPlayer.Heading));
                    offScreenDC.DrawImage(bmpImg,
                        OffsetY(selectPlayer.Location.Y, GPlayerSelf.Me.Location.Y) - bmpImg.Width / 2,
                        OffsetX(selectPlayer.Location.X, GPlayerSelf.Me.Location.X) - bmpImg.Height / 2);
                    PrintUnitInfo(tempColor, 
                        OffsetY(selectPlayer.Location.Y, GPlayerSelf.Me.Location.Y),
                        OffsetX(selectPlayer.Location.X, GPlayerSelf.Me.Location.X),
                        othTop, othBot);
                }
                else
                {
                    PrintArrow(tempColor, OffsetY(selectPlayer.Location.Y, GPlayerSelf.Me.Location.Y),
                        OffsetX(selectPlayer.Location.X, GPlayerSelf.Me.Location.X),
                        selectPlayer.Heading, othTop, othBot);
                }
            }
            if (playFriends) playASound("Friends", MyAM.soundfileFriends);
            if (playEnemies) playASound("Enemies", MyAM.soundfileEnemies);
        }

        // Print monsters
        private void PrintMonsters()
        {
            Color tempColor;
            string othTop = "";
            string othBot = "";
            bool useFilters = false;
            bool playNPCs = false;
            bool playMonsters = false;

            useFilters = (MyAM.useFilters && (MyAM.FilterMonsters.Length > 0));
            //Print monsters

            foreach (GMonster selectMonster in GObjectList.GetMonsters())
            {
                if (selectMonster.IsDead)
                    continue;

                if (MyAM.showOnlyEliteMonsters && !selectMonster.IsElite)
                    continue;
 
                if (MyAM.showNPCs && selectMonster.Reaction == GReaction.Friendly)
                {
                    if (useFilters && (!rFilterNPCs.Match(selectMonster.Name).Success))
                        continue;
                    tempColor = MyAM.colorNPCs; // DefColors.FriendMonster;
                }
                else if (selectMonster.Level < iMonsterMinLvl)
                    continue;
                else if (MyAM.showHostiles && selectMonster.Reaction == GReaction.Hostile)
                {
                    if (useFilters && (!rFilterMonsters.Match(selectMonster.Name).Success))
                        continue;
                    tempColor = MyAM.colorHostiles; // DefColors.EnemyMonster;
                }
                else if (MyAM.showNeutrals && selectMonster.Reaction == GReaction.Neutral)
                {
                    if (useFilters && (!rFilterMonsters.Match(selectMonster.Name).Success))
                        continue;
                    tempColor = MyAM.colorNeutrals; // DefColors.NeutralMonster;
                }
                else if (MyAM.showUnknownMonsters && selectMonster.Reaction == GReaction.Unknown)
                    tempColor = MyAM.colorUnknownMonsters; // DefColors.UnknownMonster;
                else
                    continue;

                if (MyAM.playSound)
                {
                    if ((selectMonster.Reaction == GReaction.Friendly) && (MyAM.soundfileNPCs.Length > 0))
                        playNPCs = true;
                    if ((selectMonster.Reaction != GReaction.Friendly) && (MyAM.soundfileMonsters.Length > 0))
                        playMonsters = true;
                }

                if (MyAM.showNames) othTop = selectMonster.Name;
                if (selectMonster.IsElite)
                    othBot = selectMonster.Level.ToString() +  "E ";
                else
                    othBot = selectMonster.Level.ToString() + " ";
                if (MyAM.showClass) othBot = othBot + CreatureType(selectMonster.CreatureType) + " "; 
                if (MyAM.showHealth) othBot = othBot + Convert.ToInt16(selectMonster.Health * 100).ToString(); 
                othBot = othBot.TrimEnd();

                if (MyAM.sourceIcons == IconSource.UseIcons)
                {
                    Bitmap bmpImg = RotateImage(img, ConvertHeadingDeg(selectMonster.Heading));
                    offScreenDC.DrawImage(bmpImg,
                        OffsetY(selectMonster.Location.Y, GPlayerSelf.Me.Location.Y) - bmpImg.Width / 2,
                        OffsetX(selectMonster.Location.X, GPlayerSelf.Me.Location.X) - bmpImg.Height / 2);
                    PrintUnitInfo(tempColor, 
                        OffsetY(selectMonster.Location.Y, GPlayerSelf.Me.Location.Y),
                        OffsetX(selectMonster.Location.X, GPlayerSelf.Me.Location.X),
                        othTop, othBot);
                }
                else
                {
                    PrintArrow(tempColor, OffsetY(selectMonster.Location.Y, GPlayerSelf.Me.Location.Y),
                    OffsetX(selectMonster.Location.X, GPlayerSelf.Me.Location.X),
                    selectMonster.Heading, othTop, othBot);
                }
            }
            if (playNPCs) playASound("NPCs", MyAM.soundfileNPCs);
            if (playMonsters) playASound("Monsters", MyAM.soundfileMonsters);
        }

        // Print Nodes (minerals, herbs, etc)
        private void PrintNodes()
        {
            Color tempColor;
            bool playHerb = false;
            bool playMineral = false;
            bool playTreasure = false;
            bool playMisc = false;
 
            //Print nodes
            foreach (GNode selectNode in GObjectList.GetNodes())
            {
                if (MyAM.showHerbs && selectNode.IsFlower)
                {
                    if (MyAM.useFilters && (MyAM.FilterHerbs.Length > 0) && (!rFilterHerbs.Match(selectNode.Name).Success))
                        continue;
                    tempColor = MyAM.colorHerbs; // DefColors.Herb;
                    if (MyAM.playSound && (MyAM.soundfileHerbs.Length > 0)) playHerb = true;
                }
                else if (MyAM.showMinerals && selectNode.IsMineral)
                {
                    if (MyAM.useFilters && (MyAM.FilterMinerals.Length > 0) && (!rFilterMinerals.Match(selectNode.Name).Success))
                        continue;
                    tempColor = MyAM.colorMinerals; // DefColors.Mineral;
                    if (MyAM.playSound && (MyAM.soundfileMinerals.Length > 0)) playMineral = true;
                }
                else if (MyAM.showTreasure && selectNode.IsTreasure)
                {
                    if (MyAM.useFilters && (MyAM.FilterTreasure.Length > 0) && (!rFilterTreasure.Match(selectNode.Name).Success))
                        continue;
                    tempColor = MyAM.colorTreasure; // DefColors.Treasure;
                    if (MyAM.playSound && (MyAM.soundfileTreasure.Length > 0)) playTreasure = true;
                }
                else if (MyAM.showMisc)
                {
                    if (MyAM.useFilters && (MyAM.FilterMisc.Length > 0) && (!rFilterMisc.Match(selectNode.Name).Success))
                        continue;
                    tempColor = MyAM.colorMisc; // DefColors.Misc;
                    if (MyAM.playSound && (MyAM.soundfileMisc.Length > 0)) playMisc = true;
                }
                else
                    continue;

                PrintCircle(tempColor, OffsetY(selectNode.Location.Y, GPlayerSelf.Me.Location.Y),
                    OffsetX(selectNode.Location.X, GPlayerSelf.Me.Location.X),
                    selectNode.Name);
            }
            if (playHerb) playASound(" herbs",MyAM.soundfileHerbs);
            if (playMineral) playASound(" minerals", MyAM.soundfileMinerals);
            if (playTreasure) playASound("treasure", MyAM.soundfileTreasure);
            if (playMisc) playASound(" misc", MyAM.soundfileMisc);
        }

        //Used to print all data to map
        private void UpdateThread()
        {
            DateTime diff = DateTime.Now;
            bool bFirstTime = true;

            string zone;
            Int32 cycleCnt = 0;

            // Update the Windows Configuration First
            UpdateUIVars();

            // Really rough hack to test icons rather than arrows
            if (MyAM.sourceIcons == IconSource.UseIcons)
            {
                MpqManager mgr = new MpqManager();
                img = mgr.ExtractBmp("Interface\\Minimap\\MinimapArrow.blp", AppDomain.CurrentDomain.BaseDirectory + "\\Classes\\Maps\\arrow.jpg");
            }
            //while (GContext.Main.IsAttached)
            while (true)
            {
                if (GContext.Main.IsAttached)
                {
                    if (bFirstTime) 
                    {
                        // Need to make sure Glider Attach is running before updating config vars;
                        UpdateConfigVars();
                        bFirstTime = false;
                    }

                    try
                    {
                        zone = GContext.Main.WorldMap;

                        /*if (zone == "None")
                        {
                            offScreenDC = Graphics.FromImage(offScreenBmp);
                            offScreenDC.FillRectangle(new SolidBrush(Color.Gray), // Grey out box
                                new Rectangle(0, 0, offScreenBmp.Width, offScreenBmp.Height));
                        }
                        else
                        {*/
                            // Load map array into offScreenDC

                        // Bitmap to write to
                        if (UpdateFormSize)
                        {
                            offScreenBmp = new Bitmap(this.Width, this.Height);
                            UpdateFormSize = false;
                        }

                        if (MyAM.cycleFilter)
                        {
                            cycleCnt++;
                            if (cycleCnt < MyAM.cyclesFilterOn)
                            {
                                MyAM.useFilters = true;
                            }
                            else if (cycleCnt < (MyAM.cyclesFilterOn + MyAM.cyclesFilterOff))
                            {
                                MyAM.useFilters = false;
                            }
                            else
                            {
                                 cycleCnt = 0;
                            }
                            if (cycleCnt == 1 || cycleCnt == (MyAM.cyclesFilterOn + 1))
                            {
                                SetButtonColor(this.btnUseFilters, MyAM.useFilters);
                            }

                        }

                        offScreenDC = tiles.GetBackground(zone, TileX(), TileY(), offScreenBmp,
                            this.Width / 2 - (int)(PlayerPosX() % TILESIZE * (IMAGESIZE / TILESIZE) * (float)MyAM.scale),
                            this.Height / 2 - (int)(PlayerPosY() % TILESIZE * (IMAGESIZE / TILESIZE) * (float)MyAM.scale),
                            MyAM.scale);
                        //}

                        PrintMe();

                        if (MyAM.specialSearchString == "")
                        {
                            if (showPlayers)
                                PrintPlayers();

                            if (showMonsters)
                                PrintMonsters();

                            if (showNodes)
                                PrintNodes();

                            if (MyAM.showWaypoints)
                                PrintWaypoints();

                        }
                        else
                        {
                            string othTop = "";
                            string othBot = "";
                            foreach (GUnit selectUnit in GObjectList.GetUnits())
                            {
                                if (selectUnit.Name.Contains(MyAM.specialSearchString))
                                {
                                    if (MyAM.showNames) othTop = selectUnit.Name;
                                    othBot = selectUnit.Level.ToString() + " ";
                                    if (MyAM.showHealth) othBot = othBot + Convert.ToInt16(selectUnit.Health * 100).ToString();
                                    othBot = othBot.TrimEnd();
                                    PrintArrow(MyAM.colorSpecial, OffsetY(selectUnit.Location.Y, GPlayerSelf.Me.Location.Y),
                                    OffsetX(selectUnit.Location.X, GPlayerSelf.Me.Location.X),
                                    selectUnit.Heading, othTop, othBot);
                                    if (MyAM.playSound && (MyAM.soundfileSpecial.Length > 0))
                                    {
                                        playASound(MyAM.specialSearchString, MyAM.soundfileSpecial);
                                    }

                                }
                            }
                        }
                        // Prepare graphics
                        Graphics clientDC = this.CreateGraphics();

                        //Display result
                        clientDC.DrawImage(offScreenBmp, 0, 0);
//                        clientDC.DrawImage(offScreenBmp, 0, 0, this.Width, this.Height);

                        // Update title
                        this.Text = PlayerX() + ", " + PlayerY() + " - Map FPS: " + Convert.ToString((float)1 / ((float)(DateTime.Now.Ticks - diff.Ticks) / (float)10000000));
                        diff = DateTime.Now;
                    }
                    catch (Exception e)
                    {
                        Log(e.ToString());
                    }
                }
                else
                    Thread.Sleep(1000);
                Thread.Sleep(MyAM.ThreadThrottle);
            }
        }

        /// <summary>
        /// Creates a new Image containing the same image only rotated
        /// </summary>
        /// <param name="image">The <see cref="System.Drawing.Image"/> to rotate</param>
        /// <param name="angle">The amount to rotate the image, clockwise, in degrees</param>
        /// <returns>A new <see cref="System.Drawing.Bitmap"/> that is just large enough
        /// to contain the rotated image without cutting any corners off.</returns>
        /// <exception cref="System.ArgumentNullException">Thrown if <see cref="image"/> is null.</exception>
        public static Bitmap RotateImage(Image image, float angle)
        {
            if (image == null)
                throw new ArgumentNullException("image");

            

            const double pi2 = Math.PI / 2.0;

            // Why can't C# allow these to be const, or at least readonly
            // *sigh*  I'm starting to talk like Christian Graus :omg:
            double oldWidth = (double)image.Width;
            double oldHeight = (double)image.Height;

            // Convert degrees to radians
            double theta = ((double)angle) * Math.PI / 180.0;
            double locked_theta = theta;

            // Ensure theta is now [0, 2pi)
            while (locked_theta < 0.0)
                locked_theta += 2 * Math.PI;

            double newWidth, newHeight;
            int nWidth, nHeight; // The newWidth/newHeight expressed as ints

            #region Explaination of the calculations
            /*
			 * The trig involved in calculating the new width and height
			 * is fairly simple; the hard part was remembering that when 
			 * PI/2 <= theta <= PI and 3PI/2 <= theta < 2PI the width and 
			 * height are switched.
			 * 
			 * When you rotate a rectangle, r, the bounding box surrounding r
			 * contains for right-triangles of empty space.  Each of the 
			 * triangles hypotenuse's are a known length, either the width or
			 * the height of r.  Because we know the length of the hypotenuse
			 * and we have a known angle of rotation, we can use the trig
			 * function identities to find the length of the other two sides.
			 * 
			 * sine = opposite/hypotenuse
			 * cosine = adjacent/hypotenuse
			 * 
			 * solving for the unknown we get
			 * 
			 * opposite = sine * hypotenuse
			 * adjacent = cosine * hypotenuse
			 * 
			 * Another interesting point about these triangles is that there
			 * are only two different triangles. The proof for which is easy
			 * to see, but its been too long since I've written a proof that
			 * I can't explain it well enough to want to publish it.  
			 * 
			 * Just trust me when I say the triangles formed by the lengths 
			 * width are always the same (for a given theta) and the same 
			 * goes for the height of r.
			 * 
			 * Rather than associate the opposite/adjacent sides with the
			 * width and height of the original bitmap, I'll associate them
			 * based on their position.
			 * 
			 * adjacent/oppositeTop will refer to the triangles making up the 
			 * upper right and lower left corners
			 * 
			 * adjacent/oppositeBottom will refer to the triangles making up 
			 * the upper left and lower right corners
			 * 
			 * The names are based on the right side corners, because thats 
			 * where I did my work on paper (the right side).
			 * 
			 * Now if you draw this out, you will see that the width of the 
			 * bounding box is calculated by adding together adjacentTop and 
			 * oppositeBottom while the height is calculate by adding 
			 * together adjacentBottom and oppositeTop.
			 */
            #endregion

            double adjacentTop, oppositeTop;
            double adjacentBottom, oppositeBottom;

            // We need to calculate the sides of the triangles based
            // on how much rotation is being done to the bitmap.
            //   Refer to the first paragraph in the explaination above for 
            //   reasons why.
            if ((locked_theta >= 0.0 && locked_theta < pi2) ||
                (locked_theta >= Math.PI && locked_theta < (Math.PI + pi2)))
            {
                adjacentTop = Math.Abs(Math.Cos(locked_theta)) * oldWidth;
                oppositeTop = Math.Abs(Math.Sin(locked_theta)) * oldWidth;

                adjacentBottom = Math.Abs(Math.Cos(locked_theta)) * oldHeight;
                oppositeBottom = Math.Abs(Math.Sin(locked_theta)) * oldHeight;
            }
            else
            {
                adjacentTop = Math.Abs(Math.Sin(locked_theta)) * oldHeight;
                oppositeTop = Math.Abs(Math.Cos(locked_theta)) * oldHeight;

                adjacentBottom = Math.Abs(Math.Sin(locked_theta)) * oldWidth;
                oppositeBottom = Math.Abs(Math.Cos(locked_theta)) * oldWidth;
            }

            newWidth = adjacentTop + oppositeBottom;
            newHeight = adjacentBottom + oppositeTop;

            nWidth = (int)Math.Ceiling(newWidth);
            nHeight = (int)Math.Ceiling(newHeight);

            Bitmap rotatedBmp = new Bitmap(nWidth, nHeight);

            using (Graphics g = Graphics.FromImage(rotatedBmp))
            {
                // This array will be used to pass in the three points that 
                // make up the rotated image
                Point[] points;

                /*
                 * The values of opposite/adjacentTop/Bottom are referring to 
                 * fixed locations instead of in relation to the
                 * rotating image so I need to change which values are used
                 * based on the how much the image is rotating.
                 * 
                 * For each point, one of the coordinates will always be 0, 
                 * nWidth, or nHeight.  This because the Bitmap we are drawing on
                 * is the bounding box for the rotated bitmap.  If both of the 
                 * corrdinates for any of the given points wasn't in the set above
                 * then the bitmap we are drawing on WOULDN'T be the bounding box
                 * as required.
                 */
                if (locked_theta >= 0.0 && locked_theta < pi2)
                {
                    points = new Point[] { 
											 new Point( (int) oppositeBottom, 0 ), 
											 new Point( nWidth, (int) oppositeTop ),
											 new Point( 0, (int) adjacentBottom )
										 };

                }
                else if (locked_theta >= pi2 && locked_theta < Math.PI)
                {
                    points = new Point[] { 
											 new Point( nWidth, (int) oppositeTop ),
											 new Point( (int) adjacentTop, nHeight ),
											 new Point( (int) oppositeBottom, 0 )						 
										 };
                }
                else if (locked_theta >= Math.PI && locked_theta < (Math.PI + pi2))
                {
                    points = new Point[] { 
											 new Point( (int) adjacentTop, nHeight ), 
											 new Point( 0, (int) adjacentBottom ),
											 new Point( nWidth, (int) oppositeTop )
										 };
                }
                else
                {
                    points = new Point[] { 
											 new Point( 0, (int) adjacentBottom ), 
											 new Point( (int) oppositeBottom, 0 ),
											 new Point( (int) adjacentTop, nHeight )		
										 };
                }

                g.DrawImage(image, points);
            }

            return rotatedBmp;
        }

        // Prints specified color arrow to screen facing heading (0 - right, PI/2 - down, PI - left, 3PI/2 - up)
        private void PrintArrow(Color color, int x, int y, double heading /*radians*/,
            string topString, string botString)
        {
            try
            {
                heading = ConvertHeading(heading);

                //Define arrow/rotation
                Point[] arrow = new Point[5];

                arrow[0] = new Point(Convert.ToInt32(x + Math.Cos(heading) * 10),
                    Convert.ToInt32(y + Math.Sin(heading) * 10));

                arrow[1] = new Point(Convert.ToInt32(x + Math.Cos(heading + Math.PI * 2 / 3) * 5),
                    Convert.ToInt32(y + Math.Sin(heading + Math.PI * 2 / 3) * 5));

                arrow[2] = new Point(x, y);

                arrow[3] = new Point(Convert.ToInt32(x + Math.Cos(heading + Math.PI * 2 / -3) * 5),
                    Convert.ToInt32(y + Math.Sin(heading + Math.PI * 2 / -3) * 5));

                arrow[4] = new Point(Convert.ToInt32(x + Math.Cos(heading) * 10),
                    Convert.ToInt32(y + Math.Sin(heading) * 10));

                //Do whatever should be printed

                //Print arrow
                offScreenDC.DrawLines(new Pen(color), arrow);

                //Print Top String
                if (topString.Length > 0)
                {
                    offScreenDC.DrawString(topString, MyAM.FontText, new SolidBrush(color),
                    new PointF(x - topString.Length * (float)2.2, y - 15));
                }
                //Print Bottom String
                if (botString.Length > 0)
                {
                    offScreenDC.DrawString(botString, MyAM.FontText, new SolidBrush(color),
                    new PointF(x - botString.Length * 2, y + 6));
                }
                //End
            }
            catch (Exception e)
            {
                Log(e.ToString());
            }
        }

        private void PrintUnitInfo(Color color, int x, int y, string topString, string botString)
        {
            try
            {
                //Print Top String
                if (topString.Length > 0)
                {
                    offScreenDC.DrawString(topString, MyAM.FontText, new SolidBrush(color),
                    new PointF(x - topString.Length * (float)2.2, y - 15));
                }
                //Print Bottom String
                if (botString.Length > 0)
                {
                    offScreenDC.DrawString(botString, MyAM.FontText, new SolidBrush(color),
                    new PointF(x - botString.Length * 2, y + 6));
                }
                //End
            }
            catch (Exception e)
            {
                Log(e.ToString());
                Log(e.Message);
                Log(e.StackTrace);
            }
        }

        // Print circle
        private void PrintCircle(Color color, int x, int y,
            string name)
        {
            try
            {
                int radius = 3;

                //Do whatever should be printed

                //Print arrow
                offScreenDC.DrawEllipse(new Pen(color), x - radius, y - radius, 2 * radius, 2 * radius);

                //Print Name
                if (MyAM.showNames)
                {
                    offScreenDC.DrawString(name, MyAM.FontText, new SolidBrush(color),
                    new PointF(x - name.Length * 2, y - 15));
                }
                //End
            }
            catch
            {
                Log("Error in PrintCircle()");
            }
        }

        // Doesnt work
        private void PrintWaypoints()
        {
            
        }

        // Generate random string (all cap letters, specified length)
        private string RandomString(int digits)
        {
            string temp = "";
            for (int i = 0; i < digits; i++)
            {
                temp += (char)rnd.Next(65, 90);
            }
            return temp;
        }

        // Log with time and threading debug
        private void GLog(string print)
        {
            string temp = "";
            if (time)
                temp += String.Format("{0} - ", DateTime.Now.ToString("hh:mm:ss.mm"));
            if (debug)
                temp += Convert.ToString(Thread.CurrentThread.GetHashCode()) + " - ";
            temp += print;
            GContext.Main.Log(temp);
        }

        public void Log(string print)
        {

            if (MyAM.loggingStatus)
            {

                string temp = "";
                string file = AppDomain.CurrentDomain.BaseDirectory + "\\Classes\\Maps\\Log.txt";

                try
                {
                    TextWriter output = TextWriter.Synchronized(File.AppendText(file));

                    if (time)
                        temp += String.Format("{0} - ", DateTime.Now.ToString("hh:mm:ss.mm"));
                    if (debug)
                        temp += Convert.ToString(Thread.CurrentThread.GetHashCode()) + " - ";
                    temp += print;
                    output.WriteLine(temp);
                    output.Close();
                }
                catch (Exception ex)
                {
                    GLog("Error in SaveToFile()");
                    GLog(ex.StackTrace);
                    GLog(ex.Message);
                }
            }
            else
            {
                GLog(print);
            }
        }
        #endregion

        #region onEvent

        private void zoom_in_Click(object sender, EventArgs e)
        {
            MyAM.scale += 0.3;
        }

        private void zoom_out_Click(object sender, EventArgs e)
        {
            if (MyAM.scale > 0.3)
                MyAM.scale -= 0.3;
        }

        private void loggingToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MyAM.loggingStatus = loggingToolStripMenuItem.Checked;
        }

        private void alwaysOnTopToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.TopMost = alwaysOnTopToolStripMenuItem.Checked;
            MyAM.onTop = !MyAM.onTop;
        }

        private void namesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MyAM.showNames = namesToolStripMenuItem.Checked;
        }

        private void classesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MyAM.showClass = classesToolStripMenuItem.Checked;
        }

        private void healthToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MyAM.showHealth = healthToolStripMenuItem.Checked;
        }

        private void settingsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Config MyConfig = new Config();
            MyConfig.SetGridObject(MyAM);
            //MyAM.Load<AMSettings>(targetFile);
            object modalResult = MyConfig.ShowDialog();
            if ((int)modalResult == 1)
            {
                // Save & Update Values
                MyAM.Save(targetFile);
                UpdateConfigVars();
                UpdateUIVars();
            }
            MyConfig.Close();
            MyConfig.Dispose();

        }


        private void btnUseFilters_Click(object sender, EventArgs e)
        {
            MyAM.useFilters = !MyAM.useFilters;
            SetButtonColor(btnUseFilters, MyAM.useFilters);
        }

        private void btnSound_Click(object sender, EventArgs e)
        {
            MyAM.playSound = !MyAM.playSound;
            SetButtonColor(btnSound, MyAM.playSound);
        }

        private void getInfoToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MessageBox.Show(this.Location.X + ", " + this.Location.Y + " - " + this.Size.Width + ", " + this.Size.Height, "Info");
        }

        private void transparent_Click(object sender, EventArgs e)
        {
            if (this.Opacity >= 0.2)
                this.Opacity = this.Opacity - 0.1;
            MyAM.opacity = this.Opacity;
        }

        private void opaque_Click(object sender, EventArgs e)
        {
            if (this.Opacity <= 0.9)
                this.Opacity = this.Opacity + 0.1;
            MyAM.opacity = this.Opacity;
        }

        private void map_MouseWheel(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (e.Delta > 0)
            {
                //zoom in
                if (MyAM.scale > 2)
                    MyAM.scale += 0.6;
                else
                    MyAM.scale += 0.3;
            }
            else
            {
                //zoom out
                if (MyAM.scale > 2)
                {
                    MyAM.scale -= 0.6;
                }
                else if (MyAM.scale > 0.3)
                {
                    MyAM.scale -= 0.3;
                }
                    
            }
        }

        private void playASound( string forWho, string waveFilename)
        {
            Log("Found " + forWho + " playing: " + waveFilename);
            soundQueue.EnqueueTask(waveFilename);
        }


        private void UpdateConfigVars()
        {
            if (MyAM.MonsterMinLvl < 0)
                iMonsterMinLvl = GPlayerSelf.Me.Level + MyAM.MonsterMinLvl;
            else if (MyAM.MonsterMinLvl == 0)
                iMonsterMinLvl = 0;
            else
                iMonsterMinLvl = MyAM.MonsterMinLvl;
            // Dim rx As New Regex("\b(?<word>\w+)\s+(\k<word>)\b", RegexOptions.Compiled Or RegexOptions.IgnoreCase)
            if (MyAM.FilterPlayers.Length > 0)
                rFilterPlayers = new Regex(MyAM.FilterPlayers, RegexOptions.Compiled | RegexOptions.IgnoreCase);
            if (MyAM.FilterNPCs.Length > 0)
                rFilterNPCs = new Regex(MyAM.FilterNPCs, RegexOptions.Compiled | RegexOptions.IgnoreCase);
            if (MyAM.FilterMonsters.Length > 0)
                rFilterMonsters = new Regex(MyAM.FilterMonsters, RegexOptions.Compiled | RegexOptions.IgnoreCase);
            if (MyAM.FilterHerbs.Length > 0)
                rFilterHerbs = new Regex(MyAM.FilterHerbs, RegexOptions.Compiled | RegexOptions.IgnoreCase);
            if (MyAM.FilterMinerals.Length > 0)
                rFilterMinerals = new Regex(MyAM.FilterMinerals, RegexOptions.Compiled | RegexOptions.IgnoreCase);
            if (MyAM.FilterTreasure.Length > 0)
                rFilterTreasure = new Regex(MyAM.FilterTreasure, RegexOptions.Compiled | RegexOptions.IgnoreCase);
            if (MyAM.FilterMisc.Length > 0)
                rFilterMisc = new Regex(MyAM.FilterMisc, RegexOptions.Compiled | RegexOptions.IgnoreCase);
            showPlayers = MyAM.showFriends || MyAM.showEnemies;
            showMonsters = MyAM.showNPCs || MyAM.showHostiles || MyAM.showNeutrals;
            showNodes = MyAM.showHerbs || MyAM.showTreasure || MyAM.showMinerals || MyAM.showMisc;
        }

        private void UpdateUIVars()
        {
            this.SuspendLayout();
            this.Opacity = MyAM.opacity;
            Point tempPoint = new Point(MyAM.clientX, MyAM.clientY);
            this.DesktopLocation = tempPoint;
            this.TopMost = MyAM.onTop;
            this.Height = MyAM.clientHeight;
            this.Width = MyAM.clientWidth;
            this.ResumeLayout(false);
            this.toolStripTextBox1.Text = MyAM.specialSearchString;
            this.loggingToolStripMenuItem.Checked = MyAM.loggingStatus;
            this.alwaysOnTopToolStripMenuItem.Checked = MyAM.onTop;
            this.namesToolStripMenuItem.Checked = MyAM.showNames;
            this.classesToolStripMenuItem.Checked = MyAM.showClass;
            this.healthToolStripMenuItem.Checked = MyAM.showHealth;
        }

        private void Map_Resize(object sender, EventArgs e)
        {
            UpdateFormSize = true;
            MyAM.clientWidth = this.Width;
            MyAM.clientHeight = this.Height;
        }

        private void Map_Move(object sender, EventArgs e)
        {
                MyAM.clientX = (this.DesktopLocation.X < 10 ? 10 : this.DesktopLocation.X);
                MyAM.clientY = (this.DesktopLocation.Y < 10 ? 10 : this.DesktopLocation.Y);
        }

        private void SetButtonColor(Button myButton, bool myValue)
        {
            if (myValue == true)
            {
                myButton.BackColor = Color.LightGreen;
            }
            else
            {
                myButton.BackColor = Color.LightSalmon;
            }
        }
   
        #endregion

        private void toolStripTextBox1_Leave(object sender, EventArgs e)
        {
            MyAM.specialSearchString = this.toolStripTextBox1.Text;
        }


    }
}
